diskbench: bypass the page cache on Linux, and say so when it cannot - #25
Merged
Conversation
`diskbench` documented itself as measuring cache-bypassed reads (F_NOCACHE / O_DIRECT) and on Linux did neither: `nocache()` had an `#ifdef __APPLE__` body and nothing else in it, and O_DIRECT appeared nowhere in the file. Reported against a Samsung 970 PRO on Gen3 x4 — 44.67 GB/s sequential and 65.72 GB/s random against a 3.94 GB/s link, 11x and 17x the ceiling. With the flag, 3.15 and 3.33 GB/s, saturating at two threads, which is what that drive should do. This is LEARNED §14 in the one place §14 did not reach. The engine's bypass was written blind on 2026-07-28 and fixed there; the tool that exists to characterise the engine's I/O kept measuring RAM. §46's standing rule — "before claiming anything is disk-bound, run diskbench and divide" — therefore returned a fiction on Linux for that whole window. No number in the docs is affected: every diskbench figure in GATES.md, EFFICIENCY.md and LEARNED §44/§46 was measured on macOS, where F_NOCACHE did work. The flag alone is not enough, for the reason bank_open already knows: O_DIRECT is accepted at open and refused at transfer (tmpfs does this, so would a device wanting a bigger block than we align to), so a bare flag turns a refusing filesystem into "short read -1" and a table of zeroes with no cause given. So this follows bank_open: probe with one aligned transfer, fall back to a plain open plus POSIX_FADV_RANDOM, and label every row — a bench that quietly measures something else is worse than one that says it could not. The write is bypassed too, and that is not symmetry for its own sake. Leaving it buffered — on the argument that it models the conversion landing, and that an O_DIRECT read invalidates the range anyway — was measured wrong on macOS: F_NOCACHE stops new pages being cached but does not evict resident ones, so a buffered write leaves the file in the UBC and every read row below reports RAM. 1 GB file, M5 Pro: 8.07 GB/s sequential read with the write bypassed, 26.04 GB/s with it buffered. The original `nocache()` on the write fd was load-bearing. Also: a sub-page record silently rounded to zero and divided by it, and a failed sequential read just ended the loop and shortened the row. Verified on macOS — unchanged against main within noise (7.85 vs 8.01 GB/s sequential, 6.84 vs 6.74 random 1-thread). The Linux body compiles and runs here only against stubs for O_DIRECT and posix_fadvise, which covers both the probe-succeeds and probe-refused paths and confirms the write probe restores the file byte for byte, but not the kernel's actual O_DIRECT behaviour. The 3.15/3.33 GB/s figures above are the reporter's, on hardware this machine does not have. Reported and diagnosed by fab2s in #22, with the O_DIRECT fix as written there. Co-Authored-By: fab2s <fabrice.de.stefanis@gmail.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
§14 fixed the engine's Linux bypass and left the tool that measures it, so §46's rule — before claiming anything is disk-bound, run diskbench and divide — returned a fiction on Linux for that whole window. Records the reporter's before/after against the drive's link ceiling, the F_NOCACHE finding that came out of reviewing the fix (it does not evict resident pages, so a buffered write makes every read row report RAM: 26.04 against 7.9-8.1 GB/s on the same binary), and what is still stub-verified rather than measured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rint The derived column read `-> %.2f tok/s at 12.5 GB/token cold` with 12.5 in the format string. That is K3's figure, so every run answered for K3 whatever container was being sized — ~8x off on a 48B model at 1.61 GB/token measured, and silent about the assumption, which is the part that makes it a trap rather than an approximation. It is now the fifth positional argument and has no default: without it the column is not printed at all. Defaulting to 12.5 would have kept the trap, and a tool cannot derive bytes-per-token from a scratch file — that number belongs to a container, and `waste bench` already reports it as "disk N GB total, M GB/token". The usage line says so. docs/GATES.md's Gate H table keeps its "tok/s @12.5 GB/token" header: it was a K3 decision, the figure is stated in the header rather than hidden in a format string, and the numbers under it were measured with it. Reported by fab2s alongside the O_DIRECT bug in #22. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extends #22, with that fix as the base and its author as co-author.
tools/diskbench.cdocumented itself as measuring cache-bypassed reads and on Linux did neither:nocache()had an#ifdef __APPLE__body and nothing else in it, andO_DIRECTappeared nowhere in the file. Reported by @fab2s on a Samsung 970 PRO, Gen3 x4 — 44.67 GB/s sequential and 65.72 GB/s random against a 3.94 GB/s link, 11x and 17x the ceiling; 3.15 and 3.33 GB/s with the flag, saturating at two threads.This is LEARNED §14 in the one place §14 did not reach. The engine's bypass was written blind and fixed there on 2026-07-28; the tool that exists to characterise the engine's I/O kept measuring RAM, so §46's standing rule — before claiming anything is disk-bound, run
diskbenchand divide — returned a fiction on Linux for that whole window. No published number is affected: everydiskbenchfigure indocs/GATES.md,docs/EFFICIENCY.mdand LEARNED §44/§46 was measured on macOS, whereF_NOCACHEdid work.On top of #22
The flag alone is not sufficient, for the reason
bank_openalready knows:O_DIRECTis accepted at open and refused at transfer — tmpfs does this, and so would a device wanting a bigger block than the tool aligns to. A bare flag turns such a filesystem intoshort read -1and a table of zeroes with no cause given. So it now followsbank_open: probe with one aligned transfer, fall back to a plain open plusPOSIX_FADV_RANDOM, and label every row.Bypassing the write is load-bearing, not symmetry. I had drafted a review asking to leave it buffered — row 1 stands for the conversion landing, and an
O_DIRECTread invalidates the range anyway. That holds on Linux and is wrong on macOS:F_NOCACHEstops new pages being cached but does not evict resident ones, so a buffered write leaves the file in the UBC and every read row below reports RAM. Same binary, 1 GB file, 4 MB records, differing only in the write fd's bypass:The
12.5 GB/tokencolumn, also reported in #22, is fixed here rather than left for later. It was K3's figure hardcoded into the format string, so every run answered for K3 whatever container was being sized — ~8x off on a 48B model at 1.61 GB/token — and said so nowhere. It is now the fifth positional argument with no default: without it the column is not printed at all. Defaulting to 12.5 would have kept the trap, and the tool cannot derive bytes-per-token from a scratch file — that number belongs to a container, andwaste benchalready reports it asdisk N GB total, M GB/token. The usage line points there.docs/GATES.md's Gate H table keeps itstok/s @12.5 GB/tokenheader: that was a K3 decision, the assumption is stated in the header rather than buried in a format string, and the numbers under it were measured with it.Also two silent-wrong-number paths in passing: a sub-page record rounded to zero and then divided by it, and a failed sequential read just ended the loop and shortened the row.
Verification, and its limit
macOS is unchanged against
mainwithin noise — 7.85 vs 8.01 GB/s sequential, 6.84 vs 6.74 random 1-thread, 1 GB file on the internal SSD.The Linux body compiles and runs here only against stubs for
O_DIRECTandposix_fadvise, which covers both the probe-succeeds and probe-refused paths and confirms the write probe restores the file byte for byte (1 GiB written, 1 GiB read back), but is not the kernel's realO_DIRECTbehaviour. This is the same limitation §14 recorded for the engine, for the same reason: there is no Linux on this machine. @fab2s's 3.15 / 3.33 GB/s remain the only figures here from actual Linux hardware.Not in this PR
CI's SPDX glob covers
tools/*.pyandtools/*.sh, nottools/*.c— which is whydiskbench.chas no header whilelutbw.candlutmt.cdo. Widening the glob is the actual fix; adding the header alone would drift again.🤖 Generated with Claude Code